/*
 *******************************************************************************
 *
 * Copyright (C) 2019-2020 Dialog Semiconductor.
 * This computer program includes Confidential, Proprietary Information
 * of Dialog Semiconductor. All Rights Reserved.
 *
 *******************************************************************************
 */

package com.example.suota.suota.adapters;

import android.bluetooth.BluetoothDevice;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.suota.suota.R;
import com.example.suota.suota.model.ScanAdapterItem;
import com.example.suota.suota.views.SignalBar;

import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

public class BluetoothScanAdapter extends RecyclerView.Adapter<BluetoothScanAdapter.ScanViewHolder> {

    public static final int RSSI_UNAVAILABLE = Integer.MIN_VALUE;

    private static final int LIST_UPDATE_DELAY = 500;
    private static final int LIST_UPDATE_RECHECK_DELAY = 100;

    private int layoutResourceId;
    private ArrayList<ScanAdapterItem> scanItemArrayList;
    private ArrayList<BluetoothDevice> bluetoothDeviceArrayList;
    private ScanItemClickListener scanItemClickListener;

    private AtomicBoolean handlerExecuted;
    private Handler updatePatchHandler;
    private Runnable updatePatchRunnable;
    private ConcurrentHashMap<BluetoothDevice, Integer> patchUpdate;
    private TouchDetector touchDetector;

    public interface ScanItemClickListener {
        void onScanItemClick(BluetoothDevice device, ScanAdapterItem bluetoothDeviceScanned, int rssi);
    }

    // Pass layoutsResourceId = -1 if want to use default layout
    // Scan item list and Bluetooth device list should be in parallel
    // you can pass simple empty lists and insert items using the methods provided
    public BluetoothScanAdapter(int layoutResourceId, @NonNull ArrayList<ScanAdapterItem> scanItemArrayList, @NonNull ArrayList<BluetoothDevice> bluetoothDeviceArrayList, @NonNull ScanItemClickListener scanItemClickListener) {
        this.layoutResourceId = layoutResourceId == -1 ? R.layout.suotalib_item_scan : layoutResourceId;

        this.scanItemArrayList = scanItemArrayList;
        this.bluetoothDeviceArrayList = bluetoothDeviceArrayList;
        this.scanItemClickListener = scanItemClickListener;

        updatePatchHandler = new Handler();
        handlerExecuted = new AtomicBoolean(true);
        updatePatchRunnable = () -> {
            updatePatch();
            handlerExecuted.set(true);
        };
        patchUpdate = new ConcurrentHashMap<>();
    }

    @Override
    public ScanViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(layoutResourceId, parent, false);
        return new ScanViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ScanViewHolder holder, int position) {
        final BluetoothDevice bluetoothDevice = bluetoothDeviceArrayList.get(position);
        final ScanAdapterItem scanItem = scanItemArrayList.get(position);

        if (holder.pairedImage != null)
            holder.pairedImage.setVisibility(scanItem.isPaired() ? View.VISIBLE : View.GONE);
        if (holder.name != null)
            holder.name.setText(scanItem.getName() != null ? scanItem.getName() : "N/A");
        if (holder.address != null)
            holder.address.setText(scanItem.getAddress());
        if (holder.rssi != null)
            holder.rssi.setText(scanItem.getRssi() == RSSI_UNAVAILABLE ? "" : scanItem.getRssi() + " dB");
        if (holder.signalBar != null) {
            holder.signalBar.setState(scanItem.getRssi() == RSSI_UNAVAILABLE);
            holder.signalBar.setRssi(scanItem.getRssi());
        }
        if (holder.root != null) {
            holder.root.setOnClickListener(view -> scanItemClickListener.onScanItemClick(bluetoothDevice, scanItem, scanItem.getRssi()));
        }
    }

    @Override
    public int getItemCount() {
        return scanItemArrayList != null ? scanItemArrayList.size() : 0;
    }

    public void swapData(@NonNull ArrayList<ScanAdapterItem> scanItemArrayList, @NonNull ArrayList<BluetoothDevice> bluetoothDeviceArrayList) {
        clearPatchUpdate();
        this.scanItemArrayList = scanItemArrayList;
        this.bluetoothDeviceArrayList = bluetoothDeviceArrayList;
        notifyDataSetChanged();
    }

    public boolean dataSetContains(BluetoothDevice bluetoothDevice) {
        return bluetoothDeviceArrayList.contains(bluetoothDevice);
    }

    public boolean dataSetContains(ScanAdapterItem scanAdapterItem) {
        return scanItemArrayList.contains(scanAdapterItem);
    }

    public void insertItem(@NonNull BluetoothDevice bluetoothDevice, @NonNull ScanAdapterItem bluetoothDeviceScanned) {
        if (bluetoothDeviceArrayList.contains(bluetoothDevice)) {
            updateItem(bluetoothDevice, bluetoothDeviceScanned.getRssi());
            return;
        }

        bluetoothDeviceArrayList.add(bluetoothDevice);
        scanItemArrayList.add(bluetoothDeviceScanned);
        notifyItemInserted(getItemCount() - 1);
    }

    public boolean updateItem(BluetoothDevice bluetoothDevice, int rssi) {
        if (!bluetoothDeviceArrayList.contains(bluetoothDevice))
            return false;

        if (patchUpdate.containsKey(bluetoothDevice)) {
            patchUpdate.replace(bluetoothDevice, rssi);
        } else {
            patchUpdate.put(bluetoothDevice, rssi);
        }
        if (handlerExecuted.getAndSet(false)) {
            updatePatchHandler.removeCallbacks(updatePatchRunnable);
            updatePatchHandler.postDelayed(updatePatchRunnable, LIST_UPDATE_DELAY);
        }
        return true;
    }

    public void removeItem(BluetoothDevice bluetoothDevice) {
        if (!bluetoothDeviceArrayList.contains(bluetoothDevice))
            return;

        int position = bluetoothDeviceArrayList.indexOf(bluetoothDevice);
        scanItemArrayList.remove(position);
        bluetoothDeviceArrayList.remove(bluetoothDevice);
        notifyItemRemoved(position);
        notifyItemRangeChanged(position, 1);
    }

    public void clear() {
        if (scanItemArrayList.size() != 0) {
            scanItemArrayList.clear();
            bluetoothDeviceArrayList.clear();
            notifyDataSetChanged();
        }
        if (touchDetector != null)
            touchDetector.touching = false;
        clearPatchUpdate();
    }

    public ArrayList<ScanAdapterItem> getScanItemArrayList() {
        return scanItemArrayList;
    }

    public ArrayList<BluetoothDevice> getBluetoothDeviceArrayList() {
        return bluetoothDeviceArrayList;
    }

    public void updateOnce() {
        if (handlerExecuted != null && handlerExecuted.get())
            return;

        updatePatchHandler.removeCallbacks(updatePatchRunnable);
        updatePatchHandler.post(updatePatchRunnable);
    }

    protected int getLayoutResourceId() {
        return layoutResourceId;
    }

    protected void setLayoutResourceId(int layoutResourceId) {
        this.layoutResourceId = layoutResourceId;
    }

    protected ScanItemClickListener getScanItemClickListener() {
        return scanItemClickListener;
    }

    protected void setScanItemClickListener(ScanItemClickListener scanItemClickListener) {
        this.scanItemClickListener = scanItemClickListener;
    }

    protected AtomicBoolean getHandlerExecuted() {
        return handlerExecuted;
    }

    protected void setHandlerExecuted(AtomicBoolean handlerExecuted) {
        this.handlerExecuted = handlerExecuted;
    }

    protected Handler getUpdatePatchHandler() {
        return updatePatchHandler;
    }

    protected void setUpdatePatchHandler(Handler updatePatchHandler) {
        this.updatePatchHandler = updatePatchHandler;
    }

    protected Runnable getUpdatePatchRunnable() {
        return updatePatchRunnable;
    }

    protected void setUpdatePatchRunnable(Runnable updatePatchRunnable) {
        this.updatePatchRunnable = updatePatchRunnable;
    }

    protected ConcurrentHashMap<BluetoothDevice, Integer> getPatchUpdate() {
        return patchUpdate;
    }

    protected void setPatchUpdate(ConcurrentHashMap<BluetoothDevice, Integer> patchUpdate) {
        this.patchUpdate = patchUpdate;
    }

    protected void updatePatch() {
        boolean hasChanged = false;
        for (BluetoothDevice bluetoothDevice : patchUpdate.keySet()) {
            // in case between the updateItem and the execution of updatePatch, the item gets removed
            if (!bluetoothDeviceArrayList.contains(bluetoothDevice)) {
                patchUpdate.remove(bluetoothDevice);
                continue;
            }

            hasChanged = true;
            scanItemArrayList
                    .get(bluetoothDeviceArrayList.indexOf(bluetoothDevice))
                    .setRssi(patchUpdate.remove(bluetoothDevice));
        }

        if (hasChanged)
            updateList();
    }

    protected void updateList() {
        if (touchDetector != null && touchDetector.touching) {
            updatePatchHandler.postDelayed(this::updateList, LIST_UPDATE_RECHECK_DELAY);
            return;
        }
        notifyDataSetChanged();
    }

    protected void clearPatchUpdate() {
        if (patchUpdate.size() != 0)
            patchUpdate.clear();
        updatePatchHandler.removeCallbacksAndMessages(null);
    }

    static class ScanViewHolder extends RecyclerView.ViewHolder {
        final View root;
        final TextView name;
        final TextView address;
        final TextView rssi;
        final SignalBar signalBar;
        final ImageView pairedImage;

        ScanViewHolder(View itemView) {
            super(itemView);
            root = itemView.findViewById(R.id.scan_item_root);
            name = itemView.findViewById(R.id.scan_item_name);
            address = itemView.findViewById(R.id.scan_item_address);
            rssi = itemView.findViewById(R.id.scan_item_rssi);
            signalBar = itemView.findViewById(R.id.signal_bar);
            pairedImage = itemView.findViewById(R.id.paired_icon);
        }
    }

    public void createTouchDetector(RecyclerView recyclerView) {
        touchDetector = new TouchDetector();
        recyclerView.addOnItemTouchListener(touchDetector);
    }

    private class TouchDetector extends RecyclerView.SimpleOnItemTouchListener {

        private boolean touching;
        private static final int CANCEL_TOUCH_DELAY = 2000; // ms
        private final Runnable cancelTouch = () -> touching = false;

        @Override
        public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
            int action = e.getActionMasked();
            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                updatePatchHandler.removeCallbacks(cancelTouch);
                touching = false;
            } else if (action == MotionEvent.ACTION_DOWN) {
                updatePatchHandler.postDelayed(cancelTouch, CANCEL_TOUCH_DELAY);
                touching = true;
            }
            return false;
        }
    }
}
